#include "mysqlDBConnPool.h"
#include <sstream>
#include <iostream>



unsigned int defaultHashCode(const void* hashData)
{
  return 0;
}

Connid MysqlConn::id_generator_ = 0;
MysqlConn::MysqlConn(const UrlInfo& url)
      :id_(++id_generator_)
      ,url_(url)
{
  state_ = MysqlCon_Stae_Invalid;
  conn_ = nullptr;
}

MysqlConn::~MysqlConn()
{
  releaseConn();
}

bool MysqlConn::setTransaction(bool autocommit)
{
  if(autocommit)
  {
    return 0 == exeSql("SET AUTOCOMMIT=1");
  }
  else 
  {
    return 0 == exeSql("SET AUTOCOMMIT=0");
  }
}

bool MysqlConn::initConn()
{
  if(conn_)
  {
    mysql_close(conn_);
    conn_ = nullptr;
  }

  conn_ = mysql_init(nullptr);
  if(!conn_)
  {
    LOG_ERROR("mysql init fail");
    return false;
  }

  conn_ = mysql_real_connect(conn_, url_.ip_, url_.user_, url_.pass_, url_.db_name_, url_.port_, nullptr, 0);
  if(conn_ == nullptr)
  {
    LOG_ERROR("mysql connect fail");
    return false;
  }

  mysql_set_character_set(conn_, "utf8");

  if(!setTransaction(url_.autocommit_))
  {
    mysql_close(conn_);
    LOG_ERROR("init fail");
    return false;
  }

  LOG_INFO("FFL[%s,%s,%d] mysql connect mysql ip:%s, port:%u, dbname:%s,pass:%s, user:%s ",LOG_FFL, url_.ip_, url_.port_, url_.db_name_, url_.pass_, url_.user_); 

  state_ = MysqlCon_Stae_Valid;
  alivetime_ = std::chrono::system_clock::now();
  return true;

}

bool MysqlConn::setConnUsed()
{
  if(mysql_ping(conn_) != 0)
  {
    //重新连接
    if(!initConn())
    {
      releaseConn();
      return false;
    }
  }

  state_ = MysqlCon_Stae_Used;
  usetime_ = std::chrono::system_clock::now();
  return true;
}

bool MysqlConn::releaseConn()
{
  if(conn_)
  {
    mysql_close(conn_);
  }

  state_ = MysqlCon_Stae_Invalid;
  id_ = 0;
}

int MysqlConn::exeSql(const char* sql)
{
  if(!sql || !conn_)
  {
    LOG_ERROR("sql error");
    return -1;
  }

  int ret = mysql_query(conn_, sql);
  return ret;
}

int MysqlConn::exeCreate(const char* sql)
{
  if(!sql || !conn_)
  {
    LOG_ERROR("create sql error");
    return -1;
  }

  return mysql_query(conn_, sql);
}

int MysqlConn::exeInsert(const char* tablename, const db_col* colum, const unsigned char* data)
{
  if(!tablename || !colum || !data)
  {
    LOG_ERROR("exe insert sql fail");
    return -1;
  }

  std::stringstream foo;
  foo << "INSERT INTO ";
  
  foo << tablename;
  foo << " (";
  bool first = true;
  const db_col* tmp_colum = colum;
  while(tmp_colum->name_)
  {
    if(first)
    {
      first = false;
    }
    else 
    {
      foo << ", ";
    }
    foo << tmp_colum->name_;
    tmp_colum++;
  }

  foo << ") ";

  first = true;
  tmp_colum = colum;
  foo  << "VALUE (";
  int offset = 0;
  while(tmp_colum->name_)
  {
    if(strlen(tmp_colum->name_))
    {
      if(first)
      {
        first = false;
      }
      else
      {
        foo << ", ";
      }

      switch(tmp_colum->type_)
      {
        case DB_UINT8:
          {
            //只取前8位
            unsigned char tmp = 0x00ff & *((unsigned char*)(data + offset));
            foo << tmp;
          }
          break;
        case DB_CHAR:
          {
            //只取前8位
            char tmp = 0x00ff & *((char*)(data + offset));
            foo << tmp;
          }
          break;
        case DB_UINT16:
          {
            foo << *((unsigned short*)(data + offset));
          }
          break;
        case DB_UINT32:
          {
            foo << *((unsigned int*)(data + offset));
          }
          break;
        case DB_UINT64:
          {
            foo << *((unsigned long long*)(data + offset));
          }
          break;
        case DB_FLOAT:
          {
            foo << *((float*)(data + offset));
          }
          break;
        case DB_STR:
          {
            char* str = (char*)(data + offset);
            unsigned int len = strlen((char*)(data + offset));
            unsigned int size = len < tmp_colum->size_ ? len : tmp_colum->size_;
            //必须开辟至少2*size + 1的空间
            char strData[size * 2 + 1];
            bzero(strData, size * 2 + 1);
            mysql_real_escape_string(conn_, strData, str, size);
            foo << "\'" << strData << "\'";
          }
          break;
        case DB_BIN:
          {
            //前4字节位bin长度
            unsigned int size = *((unsigned int*)(data + offset));
            //记录长度字段大小
            size += sizeof(unsigned int);
            //必须开辟至少2*size + 1的空间
            char strData[size * 2 + 1];
            bzero(strData, size * 2 + 1);
            //data + offset 把size也保存进去了
            mysql_real_escape_string(conn_, strData, (char*)(data + offset), size);
            foo << "\'" << strData << "\'";
          }
          break;
        default:
          break;
      }

      if(tmp_colum->size_)
      {
        offset += tmp_colum->size_;
      }
      else 
      {
        unsigned int size = *((unsigned int*)(data + offset));
        //需要再加上记录长度类型的大小
        //再获取data数据的时候加
        //size += sizeof(unsigned int);//如果colum->size为0，需要前一个字段记录长度，使用类型uint
        offset += size; 
      }
    }
    tmp_colum++;
  }

  foo << ")";
  
  LOG_INFO("FFL[%s, %s, %d], %s", LOG_FFL, foo.str().c_str());

  return exeSql(foo.str().c_str());
}

unsigned int MysqlConn::fetchSelectSql(const char* tablename, const db_col* colum, const char* where, const char* order, const char* limit)
{
  if(!conn_ || !tablename || !colum)
  {
    return 0;
  }

  unsigned int retSize = 0;
  std::string strSql;
  strSql += "SELECT ";
  bool first = true;
  const db_col* tmp_colum = colum;
  while(tmp_colum->name_)
  {
    if(strlen(tmp_colum->name_))
    {
      if(first)
      {
        first = false;
      }
      else 
      {
        strSql += ", ";
      }
      
      strSql += tmp_colum->name_;
      retSize += tmp_colum->size_;
    }

    tmp_colum++;
  }

  strSql += " FROM ";
  strSql += tablename;
  if(where)
  {
    strSql += " ";
    strSql += where;
  }

  if(order)
  {
    strSql += " ";
    strSql += order;
  }

  if(limit)
  {
    strSql += " ";
    strSql += limit;
  }
  
  LOG_INFO("FFL[%s,%s,%d], %s", LOG_FFL, strSql.c_str());
  if(0 == exeSql(strSql.c_str()))
  {
    return retSize;
  }
  else 
  {
    return 0;
  }
}

unsigned int MysqlConn::fullSelectDataByRow(MYSQL_ROW row, unsigned long* lengths, const db_col* colum, unsigned char* data)
{
  unsigned int i = 0;
  unsigned int offset = 0;
  const db_col* tmpColum = colum;
  while(tmpColum->name_)
  {
    if(strlen(tmpColum->name_))
    {
      switch(tmpColum->size_)
      {
        case DB_CHAR:
          {
            if(row[i])
            {
              *((char*)(data + offset)) = strtoul(row[i], (char**)NULL, 10);
            }
            else 
            {
              *((char*)(data + offset)) = 0;
            }
          }
          break;
        case DB_UINT8:
          {
            if(row[i])
            {
              *((unsigned char*)(data + offset)) = strtoul(row[i], (char**)NULL, 10);
            }
            else 
            {
              *((unsigned char*)(data + offset)) = 0;
            }
          }
          break;
        case DB_UINT16:
          {
            if(row[i])
            {
              *((unsigned short*)(data + offset)) = strtoul(row[i], (char**)NULL, 10);
            }
            else 
            {
              *((unsigned short*)(data + offset)) = 0;
            }
          }
          break;
        case DB_UINT32:
          {
            if(row[i])
            {
              *((unsigned int*)(data + offset)) = strtoul(row[i], (char**)NULL, 10);
            }
            else 
            {
              *((unsigned int*)(data + offset)) = 0;
            }
          }
          break;
        case DB_UINT64:
          {
            if(row[i])
            {
              *((unsigned long long*)(data + offset)) = strtoull(row[i], (char**)NULL, 10);
            }
            else 
            {
              *((unsigned long long*)(data + offset)) = 0LL;
            }
          }
          break;
        case DB_FLOAT:
          {
            if(row[i])
            {
              *((float*)(data + offset)) = strtof(row[i], (char**)NULL);
            }
            else 
            {
              *((float*)(data + offset)) = 0.0f;
            }
          }
          break;
        case DB_STR:
        case DB_BIN:
          {
            bzero(data + offset, tmpColum->size_);
            if(row[i])
            {
              unsigned int size = tmpColum->size_ > lengths[i] ? lengths[i] : tmpColum->size_;
              memcpy(data + offset, row[i], size);
            }
          }
          break;
      }
      i++;
    }
    if(tmpColum->size_)
    {
      offset += tmpColum->size_;
    }
    else 
    {
      //unsigned int size = *((unsigned int*)(data + offset));
      unsigned int size = *((unsigned int*)(row[i]));
      offset += size;
    }
    tmpColum++;
  }
  return offset;
}

int MysqlConn::exeSelect(const char* tablename, const db_col* colum, const char* where, const char* order, const char* limit,  unsigned char** data)
{
  *data = nullptr;
  if(!conn_|| !tablename || !colum)
  {
    LOG_ERROR("exe select sql fail");
    return -1;
  }

  unsigned int rowSize = fetchSelectSql(tablename, colum, where, order, limit);
  if(!rowSize)
  {
    LOG_ERROR("exe select sql row size is 0");
    return -1;
  }

  MYSQL_RES* result = mysql_store_result(conn_);
  if(!result)
  {
    LOG_ERROR("exe select get result fail");
    return -1;
  }

  //获得行数
  unsigned int rowNum = mysql_num_rows(result);
  if(!rowNum)
  {
    mysql_free_result(result);
    LOG_ERROR("exe select get result row num fail");
    return -1;
  }

  *data = new unsigned char[rowSize * rowNum];
  if(!data)
  {
    mysql_free_result(result);
    LOG_ERROR("exe select sql new data fail");
    return -1;
  }
  
  MYSQL_ROW row = NULL;
  unsigned char* tmpData = *data;
  while(row = mysql_fetch_row(result))
  {
    unsigned long* lengths = mysql_fetch_lengths(result);
    unsigned int fullSize = fullSelectDataByRow(row, lengths, colum, tmpData);
    if(!fullSize)
    {
      delete [] data;
      data = nullptr;
      mysql_free_result(result);
      return -1;
    }
    tmpData += fullSize;
  }

  mysql_free_result(result);
  return rowNum;

}
int MysqlConn::exeUpdate(const char* tablename, const db_col* colum, const unsigned char* data, const char* where)
{
  if(!tablename || !colum || !data)
  {
    LOG_ERROR("exe insert sql fail");
    return -1;
  }

  std::stringstream foo;
  foo <<  "UPDATE ";
  
  foo << tablename;
  foo << " SET ";

  bool first = true;
  const db_col* tmp_colum = colum;
  int offset = 0;
  while(tmp_colum->name_)
  {
    if(strlen(tmp_colum->name_))
    {
      if(first)
      {
        first = false;
      }
      else 
      {
        foo << ", ";
      }
      foo << tmp_colum->name_;
      foo << "=";
      switch(tmp_colum->type_)
      {
        case DB_UINT8:
          {
            //foo << *((unsigned char*)(data + offset));
            //取前8位
            foo << *((unsigned char*)(data + offset));
          }
          break;
        case DB_CHAR:
          {
            //foo << *((char*)(data + offset));
            foo << *((char*)(data + offset));
          }
          break;
        case DB_UINT16:
          {
            foo << *((unsigned short*)(data + offset));
          }
          break;
        case DB_UINT32:
          {
            foo << *((unsigned int*)(data + offset));
          }
          break;
        case DB_UINT64:
          {
            foo << *((unsigned long long*)(data + offset));
          }
          break;
        case DB_FLOAT:
          {
            foo << *((float*)(data + offset));
          }
          break;
        case DB_STR:
          {
            char* str = (char*)(data + offset);
            unsigned int len = strlen((char*)(data + offset));
            unsigned int size = len < tmp_colum->size_ ? len : tmp_colum->size_;
            //必须开辟至少2*size + 1的空间
            char strData[size * 2 + 1];
            bzero(strData, size * 2 + 1);
            mysql_real_escape_string(conn_, strData, str, size);
            foo << "\'" << strData << "\'";
          }
          break;
        case DB_BIN:
          {
            //前4字节位bin长度
            unsigned int size = *((unsigned int*)(data + offset));
            //记录长度字段大小
            size += sizeof(unsigned int);
            //必须开辟至少2*size + 1的空间
            char strData[size * 2 + 1];
            bzero(strData, size * 2 + 1);
            //将长度也保存进去了
            mysql_real_escape_string(conn_, strData, (char*)(data + offset), size);
            foo << "\'" << strData << "\'";
          }
          break;
        default:
          break;
      }
      foo << " ";

      if(tmp_colum->size_)
      {
        offset += tmp_colum->size_;
      }
      else 
      {
        unsigned int size = *((unsigned int*)(data + offset));
        //需要再加上记录长度类型的大小
        //在解析data时加
        //size += sizeof(unsigned int);//如果colum->size为0，需要前一个字段记录长度，使用类型uint

        offset += size; 
      }
    }
    tmp_colum++;
  }

  if(where)
  {
    foo << " WHERE ";
    foo << where;
  }

  LOG_INFO("FFL[%s, %s, %d], %s", LOG_FFL, foo.str().c_str());

  return exeSql(foo.str().c_str());

}
  
int MysqlConn::exeDelete(const char* tablename, const char* where)
{
  if(!tablename)
  {
    LOG_ERROR("sql delete err");
    return -1;
  }

  std::string sqlStr;
  sqlStr += "DELETE FROM ";
  sqlStr += tablename;

  if(where)
  {
    sqlStr += " WHERE ";
    sqlStr += where;
  }
  LOG_INFO("FFL[%s, %s, %d], %s", LOG_FFL, sqlStr.c_str());
  return exeSql(sqlStr.c_str());
}

MysqlConnPool::MysqlConnPool(hashCodeFunc hashFunc)
{
  if(hashFunc)
  {
    hashFunc_ = hashFunc;
  }
  else 
  {
    hashFunc_ = defaultHashCode;
  }
}
MysqlConnPool::~MysqlConnPool()
{

}

bool MysqlConnPool::init(const char* urlsql)
{
  putUrl(0, urlsql);
  
  //创建数据库
  Connid handle = getHandle(nullptr);
  const char* sql = "CREATE TABLE IF NOT EXISTS USERTABLE(ACCOUNT VARCHAR(6), USERNAME VARCHAR(6), PASSWD VARCHAR(6))";
  if(exeCreate(handle, sql) == -1)
  {
    LOG_ERROR("init test exeCreate fail %s", sql);
    return false;
  }
  return true;
}

bool MysqlConnPool::putUrl(unsigned int hashcode, const char* url)
{
  UrlInfo* newUrl = new UrlInfo(hashcode, url);
  if(newUrl)
  {
    MysqlConn* conn = new MysqlConn(*newUrl);
    if(conn && conn->initConn())
    {
      urls_.insert(std::make_pair(hashcode, newUrl));
      conns_.insert(std::make_pair(hashcode, conn));
      Connid id = conn->getID();
      connmap_.insert(std::make_pair(id, conn));
      
      LOG_INFO("puturl succ hashcode:%u, url:%s", hashcode, url);
    }
    else 
    {
      delete  newUrl;
      LOG_ERROR("puturl err hashcode:%u, url:%s", hashcode, url);
      return false;
    }
  }
  else 
  {
    return false;
  }
  return true;
 
}

MysqlConn* MysqlConnPool::getHandleByHashCode(unsigned int hashcode)
{
  while(true)
  {
     //MysqlConn* invalidHandle = nullptr;
     auto hps =  conns_.equal_range(hashcode);
     for(auto hp = hps.first; hp != hps.second; ++hp)
     {
       MysqlConn* handle = hp->second;
       switch(handle->getState())
       {
          case MysqlCon_Stae_Invalid:
            {
              if(handle->initConn())
              {
                if(handle->setConnUsed())
                {
                  return handle;
                }
              }
              return nullptr; 
            }
            break;
          case MysqlCon_Stae_Valid:
            {
              return handle;
            }
            break;
          case MysqlCon_Stae_Used:
            {
              auto curTime = std::chrono::system_clock::now();
              auto tt = std::chrono::duration_cast<std::chrono::seconds>(handle->getUseTime() - curTime).count();
              if(tt > limit_timout_)
              {
                LOG_ERROR("the id:%u mysql timeout:%u", handle->getID(), tt);
              }
            }
            break;
       }
        //没有对应url
       if(urls_.find(hashcode) == urls_.end() || urls_[hashcode]->url_.size() == 0)
       {
         return nullptr;
       }

       //生成一个conn
       if(conns_.size() < maxnum_)
       {
         MysqlConn* conn = new MysqlConn(*urls_[hashcode]);
         if(conn)
         {
           if(conn->initConn())
           {
             conns_.insert(std::make_pair(hashcode, conn));
             connmap_[conn->getID()] = conn;
             LOG_INFO("FFL[%s, %s, %d]add new cond id:%u",LOG_FFL, conn->getID());
             if(conn->setConnUsed())
             {
              return conn;
             }
           }
           
         }
         else 
         {
           return nullptr;
         }
       }
     }
  }
  return nullptr;
}

int MysqlConnPool::exeCreate(Connid id, const char* sql)
{
  MysqlConn* handle = getHandleByID(id);
  if(handle)
  {
    return handle->exeCreate(sql);
  }
  return -1;
}

int MysqlConnPool::exeSelect(Connid id, const char* tablename, const db_col* colum, const char* where, const char* order, const char* limit, unsigned char** data)
{

  MysqlConn* handle = getHandleByID(id);
  if(handle)
  {
    return handle->exeSelect(tablename, colum, where, order, limit, data);
  }
  return -1;
}

int MysqlConnPool::exeInsert(Connid id, const char* tablename, const db_col* colum, const unsigned char* data)
{
  MysqlConn* handle = getHandleByID(id);
  if(handle)
  {
    return handle->exeInsert(tablename, colum, data);
  }
  return -1;

}

int MysqlConnPool::exeUpdate(Connid id, const char* tablename, const db_col* colum, const unsigned char* data, const char* where)
{
  MysqlConn* handle = getHandleByID(id);
  if(handle)
  {
    return handle->exeUpdate(tablename, colum, data, where);
  }
  return -1;

}

int MysqlConnPool::exeDelete(Connid id, const char* tablename, const char* where)
{
  MysqlConn* handle = getHandleByID(id);
  if(handle)
  {
    return handle->exeDelete(tablename, where);
  }
  return -1;

}
